###########################################################################
#
#   Copyright 2010 American Public Media Group
#
#   This file is part of AIR2.
#
#   AIR2 is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   AIR2 is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with AIR2.  If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################

package AIR2::SearchServer;
use strict;
use warnings;
use Carp;
use Data::Dump;
use AIR2::Searcher;
use JSON::XS;
use HTTP::Engine::Response;
use AIR2::Searcher::ActiveSources;
use AIR2::Searcher::Sources;
use AIR2::Searcher::Inquiries;
use AIR2::Searcher::Activities;
use AIR2::Searcher::Responses;
use AIR2::Searcher::Projects;
use Data::Dump qw( dump );
use Getopt::Long;
use Pod::Usage;
use Compress::Zlib;
use Scalar::Util qw( blessed );
use Unix::PID::Tiny;
use AIR2::AuthTkt;

my $TKT_NAME = AIR2::Config->get_auth_tkt_name();

{

    # our package manager allows for persistent index connections
    # but not opened till after the child process is forked,
    # so that each connection is unique to the child (not shared).
    package AIR2::Searcher::IndexManager;
    my %indexes;
    my %index_classes = (
        'sources'        => 'AIR2::Searcher::Sources',
        'active-sources' => 'AIR2::Searcher::ActiveSources',
        'inquiries'      => 'AIR2::Searcher::Inquiries',
        'projects'       => 'AIR2::Searcher::Projects',
        'activities'     => 'AIR2::Searcher::Activities',
        'responses'      => 'AIR2::Searcher::Responses',
    );

    sub get_index {
        my ( $self, $key, $pid ) = @_;
        if ( !exists $index_classes{$key} ) {
            Carp::croak "No such index: $key";
        }

        if ( exists $indexes{"$key.$pid"} ) {
            AIR2::Searcher->log("returning cached engine for $key.$pid");
            return $indexes{"$key.$pid"};
        }

        # create it
        AIR2::Searcher->log("creating new engine for $key.$pid");
        my $index = $index_classes{$key}->new( idxname => $key );

        # cache it
        $indexes{"$key.$pid"} = $index;

        return $index;
    }

}

sub handler {
    my $auth_tkt = shift;
    my $req      = shift;
    my $params   = $req->parameters;
    AIR2::Searcher->log( dump($params) );

    my $res = HTTP::Engine::Response->new;

    #warn dump( $req->cookies );
    my $auth_cookie = $req->cookies->{$TKT_NAME}
        || $params->{$TKT_NAME};
    if ( ref($auth_cookie) ) {
        $auth_cookie = $auth_cookie->value;
    }
    if ( !$auth_cookie ) {
        AIR2::Searcher->log("Permission denied (missing cookie)");
        $res->status(401);
        return $res;
    }
    my $tkt = $auth_tkt->parse_ticket( $auth_cookie, ignore_ip => 1 );

    #warn ("auth_cookie = $auth_cookie");

    # authn check
    if (   !$auth_cookie
        or !$tkt )
    {
        AIR2::Searcher->log("Permission denied");
        $res->status(401);
        return $res;
    }
    else {
        AIR2::Searcher->log("request from $tkt->{uid}");
    }

    my $result;
    my $index = $params->{i} || 'sources';
    my $object = AIR2::Searcher::IndexManager->get_index( $index, $$ );

    # similar to HEAD check
    if ( $params->{ping} ) {
        $result->{'response'} = 'I am alive';
    }

    # delete a document
    elsif ( $params->{delete} ) {
        if ( uc( $req->method ) eq 'DELETE' ) {
            eval { $result = $object->delete_document( $params->{delete} ); };
        }
        else {
            $result->{error}     = 'HTTP method must be DELETE';
            $result->{http_code} = 400;
        }
    }

    # standard search
    else {

        # abuse params to pass authz
        $params->{auth_tkt} = $tkt->{data};

        if ( $params->{terms} ) {
            eval { $result = $object->do_term_search($params); };
        }
        elsif ( $params->{stats} ) {
            eval { $result = $object->do_stats($params); };
        }
        else {
            eval { $result = $object->do_search($params); };
        }

    }
    if ( $@ or exists $result->{error} ) {
        $res->status( $result->{http_code} || 500 );
        $result->{error} ||= "$@";
        $result->{success} = 0;
        AIR2::Searcher->log( "Error: " . $result->{error} );

        # trim the return to hide file and linenum
        $result->{error} =~ s/ at \/[\w\/\.]+ line \d+\.?.*$//s;
    }
    else {
        $result->{success} = 1;
    }

    $res->headers(
        {   content_encoding => 'utf-8',
            content_type     => 'application/json; charset=utf-8',
        },
    );
    if ( $params->{gzip} ) {
        AIR2::Searcher->log("gzip response");
        $res->headers->{content_encoding} = 'gzip';
        $res->body(
            Compress::Zlib::memGzip(
                blessed($result)
                ? "$result"
                : encode_json($result)
            )
        );
    }
    else {
        $res->body( blessed($result) ? "$result" : encode_json($result) );
    }

    return $res;
}

1;
